<?php

class DesignTimeWorkflow
{
  
  /**
   * @var DesignTimeWorkflowStartNode
   */
  protected $start_node = null;
  
  /**
   * @var DesignTimeWorkflowEndNode
   */
  protected $end_node = null;
  
  /**
   * @var AbstractDesignTimeWorkflowNode
   */
  protected $previous_node = null;
  
  /**
   * @var array
   */
  protected $nodes = array();

  /**
   * @return DesignTimeWorkflow
   */
  public function startNode ($identifier = 'start')
  {
    if (! is_null($this->start_node))
    {
      throw new WorkflowDesignTimeException('Start node already initiated.');
    }
    
    $this->start_node = new DesignTimeWorkflowStartNode($identifier);
    $this->previous_node = $this->start_node;
    $this->nodes[$this->start_node->getIdentifier()] = $this->start_node;
    
    return $this;
  }

  /**
   * @return DesignTimeWorkflow
   */
  public function endNode ($identifier = 'end')
  {
    if (! is_null($this->end_node))
    {
      throw new WorkflowDesignTimeException('End node already initiated.');
    }
    
    $this->end_node = new DesignTimeWorkflowEndNode($identifier);
    $this->previous_node->addOutNode($this->end_node);
    $this->end_node->addInNode($this->previous_node);
    $this->nodes[$this->end_node->getIdentifier()] = $this->end_node;
    $this->previous_node = null;
    
    return $this;
  }

  /**
   * @param AbstractDesignTimeWorkflowNode $node
   * @return DesignTimeWorkflow
   */
  public function followedBy (AbstractDesignTimeWorkflowNode $node)
  {
    $this->previous_node->addOutNode($node);
    $node->addInNode($this->previous_node);
    $this->nodes[$node->getIdentifier()] = $node;
    $this->previous_node = $node;
    return $this;
  }

  /**
   * @param string $identifier
   * @param mixed $param1
   * @return DesignTimeWorkflowParallelSplitWithForeachableDecorator
   */
  public function followedByParallelSplitWithForeachable ($identifier, $param1)
  {
    if (self::isForeachable($param1))
    {
      $node = new DesignTimeWorkflowParallelSplitWithForeachableNode($identifier, $param1);
      $this->previous_node->addOutNode($node);
      $node->addInNode($this->previous_node);
      $this->nodes[$node->getIdentifier()] = $node;
      $this->previous_node = $node;
      return new DesignTimeWorkflowParallelSplitWithForeachableDecorator($this, $node);
    }
    else
    {
      throw new InvalidArgumentException('Expected foreachable.');
    }
  }

  /**
   * @param AbstractRecipientDeterminationRule $recipient
   */
  public function forRecipient (AbstractRecipientDeterminationRule $recipient)
  {
    $this->previous_node->setRecipient($recipient);
    
    return $this;
  }

  /**
   * @param mixed $param
   * @return bool
   */
  public static function isForeachable ($param)
  {
    if (is_array($param))
    {
      return true;
    }
    elseif (is_object($param) && $param instanceof Traversable && $param instanceof Countable)
    {
      return true;
    }
    else
    {
      return false;
    }
  }

  /**
   * @return DesignTimeWorkflowStartNode
   */
  public function getStartNode ()
  {
    return $this->start_node;
  }

  /**
   * @return bool
   */
  public function validate ()
  {
    return true;
  }

  public function getNode ($identifier)
  {
    return $this->nodes[$identifier];
  
   // TODO phpdoc
  }
  
  public function mapIn($src_key, $target_key) {
    $this->previous_node->mapIn($src_key, $target_key);
    return $this;
  }
  
  public function addAction(DesignTimeWorkflowNodeAction $action) {
    $this->previous_node->addAction($action);
    return $this;
  }
}